-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Solana support #15428
base: develop
Are you sure you want to change the base?
Solana support #15428
Conversation
// Chain represents an EVM chain. | ||
type Chain struct { | ||
// Selectors used as canonical chain identifier. | ||
Family string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Family field. We would switch logic based on the Chain family.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking (see TODO on Environment) it'd be like this
type Environment struct {
Name string
Logger logger.Logger
ExistingAddresses AddressBook
EVMChains map[uint64]EVMChain
SolChains map[uint64]SolanaChain
NodeIDs []string
Offchain OffchainClient
}
just to avoid the partially populated structs and allow for easy top level family "switch". Imagining most of the time the changesets will be scoped to a family and just operate purely on the EVMChains or SolChains, so easily looking up all the chains in a family makes it easier. Example:
type _ deployment.Changeset[DeploySolanaConfig] = DeploySolana
func DeploySolana(e deployment.Environment, cfg DeploySolanaConfig) (deployment.ChangesetOutput, error) {
// validate config
ab := newAddressBook()
if err := deployContractCCIPRampSolana(ab, e.SolChains[cfg.ChainSelector], cfg.ContractAConfig); err != nil {
//...
}
return deployment.ChangesetOutput{AddressBook: ab}, nil
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cgruber curious wyt
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are already common aspects for every chain, like selector or family that it would be good to have under the same struct for code reuse. Also functions that depend on Chain but we may not know in advance which chain family it will interact with it will have to receive the whole Environment struct. Additionally the number of non-EVM chains will grow next year to 8 non-EVM chains so that would be 8 fields more within Environment. I would prefer to isolate that complexity within the Chain struct.
Another thing, maybe minor, is that this proposal wouldn't allow in any way to have two different Chain for the same chain selector.
I also think that right now it's not clear that if there are pieces that we can hide behind CR/CW and simplify the code base of chainlink deployments. I'm not sure we really need to use chain specific bindings. That's TBD. I already seen that, for instance, OCR3Config it's the same (or can be adapted through CR/CW config) for Solana and for EVM.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the code is hard for me to understand with this pattern
eg in keystone logic we have things now like
registryChain.EVMChain
this doesn't make sense - there is no such thing a non-EVM registry chain
and i don't understand how i'm supposed to use it.
is it ever valid to have non-empty EVM & Solana? i think no. so at every call site I have to remember which kind of thing I'm dealing with rather than instantiating the correct thing once.
i don't understand the comment above out CR/CW - those are chain agnostic but this change is adding more explicit chains.
so discounting the CR/CW discussion, then the if the concern is reusing common config fields, we can embed them in the structs ~
type CommonStuff struct {
Selector
Family
...
}
type EVMChain struct {
CommonStuff
EVMThingA
EVMThingB
}
type SolanaChain struct {
CommonStuff
SolanaThingC
}
I would expect the different families to be distinguished in the environment along the lines of what @connorwstein said above
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First, we should not be using strings for family here. We need an enum (or typed set of constants).
Alternatively could we actually use the type of the struct itself? like
struct ChainFamily[CS any] {}
var (
SOLANA_FAMILY = ChainFamily[SolanaState]{}
EVM_FAMILY = ChainFamily[EVMState]{}
// ... other families
)
Then our state could include
type Environment struct {
chains map[ChainFamily[any]]map[uint64]any
}
func (gs *Environment) GetChainState[CS any](family ChainFamily[CS], chainId uint64) (CS, error) {
var zeroState CS
if familyMap, ok := gs.chains[family]; ok {
if state, ok := familyMap[chainId]; ok {
return state.(STATE), nil // Type assertion to return the correct type
}
return zeroState, fmt.Errorf("chain with ID %d not found in family", chainId)
}
return zeroState, fmt.Errorf("family not found")
}
Then callers just call:
state, err := globalState.GetChainState(SOLANA_FAMILY, 12345)
if err != nil {
// Handle error
}
foo := state.SomeSolanaSpecificValue
Then there's no different way to access any given chain. You do have to tell it what family. We can have a simple utility to get that from the selector - map a selector chain family to a given state struct somewhere. But Reference chains are just chains, per @krehermann's concern, the structure of the state is per-family, so we can have strongly typed access to the structure, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at some of the usages of chain family, it could be a two-step thing too. GetChainFamily(SOLANA_FAMILY).Chains(selectorId)
. But the same principle works here, it's just that I misunderstood the structure of the chain family in my first example.
registryChain would just have the family we use for the registry chain, it wouldn't be chain-neutral like this. etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't a perfect adaptation for the current usage. I'm trying to roughly sketch out (a) something that we can access in a strongly typed way with generics, and (b) that allows us to access things in a more uniform way (so we dont' have a pattern of if sol { return chains.SolanaStuff } else { return chains.EvmStuff }
all over. Instead, it's "get the family, then look stuff up with reference to the family to get the strong typing." The same logic is encoded, but not in call-site if-blocks.
d2f229c
to
28cf95a
Compare
50e2fe6
to
b5e8768
Compare
b5f0c0b
to
bd2545d
Compare
bd2545d
to
7bc597b
Compare
7bc597b
to
4ef2fcf
Compare
@@ -233,12 +233,20 @@ func (c CCIPChainState) GenerateView() (view.ChainView, error) { | |||
// Offchain state always derivable from a list of nodeIds. | |||
// Note can translate this into Go struct needed for MCMS/Docs/UI. | |||
type CCIPOnChainState struct { | |||
SolanaState SolanaCCIPOnChainState | |||
EVMState EVMCCIPOnChainState |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fine as far as it goes, but it does render the logic pretty clearly chain-specific, because it has to dereference a specific field for that family. This ends up with a lot of if/then logic, in the end. Maybe that's unavoidable, insofar as it is chain specific structures in the state anyway. But this feels like a case for generics. But go's generics may not be flexible enough for a good solution here. For now, this is fine, and we can look at the if/then logic that proceeds, and re-evaluate if we want to hide this behind a more robust structure.
// Chain represents an EVM chain. | ||
type Chain struct { | ||
// Selectors used as canonical chain identifier. | ||
Family string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First, we should not be using strings for family here. We need an enum (or typed set of constants).
Alternatively could we actually use the type of the struct itself? like
struct ChainFamily[CS any] {}
var (
SOLANA_FAMILY = ChainFamily[SolanaState]{}
EVM_FAMILY = ChainFamily[EVMState]{}
// ... other families
)
Then our state could include
type Environment struct {
chains map[ChainFamily[any]]map[uint64]any
}
func (gs *Environment) GetChainState[CS any](family ChainFamily[CS], chainId uint64) (CS, error) {
var zeroState CS
if familyMap, ok := gs.chains[family]; ok {
if state, ok := familyMap[chainId]; ok {
return state.(STATE), nil // Type assertion to return the correct type
}
return zeroState, fmt.Errorf("chain with ID %d not found in family", chainId)
}
return zeroState, fmt.Errorf("family not found")
}
Then callers just call:
state, err := globalState.GetChainState(SOLANA_FAMILY, 12345)
if err != nil {
// Handle error
}
foo := state.SomeSolanaSpecificValue
Then there's no different way to access any given chain. You do have to tell it what family. We can have a simple utility to get that from the selector - map a selector chain family to a given state struct somewhere. But Reference chains are just chains, per @krehermann's concern, the structure of the state is per-family, so we can have strongly typed access to the structure, etc.
// Chain represents an EVM chain. | ||
type Chain struct { | ||
// Selectors used as canonical chain identifier. | ||
Family string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at some of the usages of chain family, it could be a two-step thing too. GetChainFamily(SOLANA_FAMILY).Chains(selectorId)
. But the same principle works here, it's just that I misunderstood the structure of the chain family in my first example.
registryChain would just have the family we use for the registry chain, it wouldn't be chain-neutral like this. etc.
// Chain represents an EVM chain. | ||
type Chain struct { | ||
// Selectors used as canonical chain identifier. | ||
Family string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't a perfect adaptation for the current usage. I'm trying to roughly sketch out (a) something that we can access in a strongly typed way with generics, and (b) that allows us to access things in a more uniform way (so we dont' have a pattern of if sol { return chains.SolanaStuff } else { return chains.EvmStuff }
all over. Instead, it's "get the family, then look stuff up with reference to the family to get the strong typing." The same logic is encoded, but not in call-site if-blocks.
Basic refactor to start introducing Solana tooling implementation.
Changes: